﻿using Devil;
using GameToolkit.BehaviourTree;
using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine.UIElements;

namespace GameToolkit.Editor
{
    public static class BehaviourTreeGraphUtils
    {
        static readonly HashSet<int> sRecursiveCache = new HashSet<int>();
        
        static StyleSheet sGraphStyles;
        public static StyleSheet GetStyleSheet()
        {
            if (sGraphStyles == null)
                sGraphStyles = AssetDatabase.LoadAssetAtPath<StyleSheet>("Packages/com.devil.gametoolkit/Editor/USS/GraphStyles.uss");
            return sGraphStyles;
        }


        static int CompareNode(INodeWithComposite a, INodeWithComposite b)
        {
            var pa = a.position;
            var pb = b.position;
            if (pa.y < pb.y)
                return -1;
            else if (pa.y > pb.y)
                return 1;
            else
                return 0;
        }

        internal static Composite[] ToComposites(MinHeap<INodeWithComposite> nodes)
        {
            var items = new Composite[nodes == null ? 0 : nodes.Count];
            var index = 0;
            while(nodes.Count > 0)
            {
                items[index++] = nodes.RemoveTop().composite;
            }
            return items;
        }

        internal static CompositeState[] ToCompositesInput(MinHeap<INodeWithComposite> nodes)
        {
            var items = new CompositeState[nodes == null ? 0 : nodes.Count];
            var index = 0;
            while (nodes.Count > 0)
            {
                var node = nodes.RemoveTop();
                CompositeState input;
                input.composite = node.composite;
                input.state = (NodeState)node.temporaryPortId;
                items[index++] = input;
            }
            return items;
        }

        internal static CompositeState ToCompositeInput(MinHeap<INodeWithComposite> nodes)
        {
            if (nodes.Count == 0)
                return default;
            var node = nodes.RemoveTop();
            CompositeState input;
            input.composite = node.composite;
            input.state = (NodeState)node.temporaryPortId;
            return input;
        }

        static void SaveGroup(Group grp, IList<BehaviourTreeAsset.Group> groups)
        {
            BehaviourTreeAsset.Group group;
            group.title = grp.title;
            var nodes = new List<Composite>();
            foreach (var item in grp.containedElements)
            {
                if (item is INodeWithComposite tnode && tnode.composite != null)
                    nodes.Add(tnode.composite);
            }
            group.composites = nodes.ToArray();
            groups.Add(group);
        }

        static void SaveTreeNode(INodeWithComposite vnode, MinHeap<INodeWithComposite> nodeBuffer, IList<Composite> graphnodes)
        {
            var composite = vnode.composite;
            if (composite == null)
                return;
            composite.m_Position = vnode.position;
            graphnodes.Add(composite);
            vnode.SaveCompositeConnections(nodeBuffer);
        }

        internal static void Save(this BehaviourTreeGraph graph, BehaviourTreeAsset asset)
        {
            var nodeBuffer = new MinHeap<INodeWithComposite>(32, CompareNode, null, true);
            var graphnodes = new List<Composite>();
            var groups = new List<BehaviourTreeAsset.Group>();
            foreach(var item in graph.graphElements)
            {
                if(item is Group grp)
                {
                    SaveGroup(grp, groups);
                }
                else if(item is INodeWithComposite vnode)
                {
                    SaveTreeNode(vnode, nodeBuffer, graphnodes);
                }
            }
            asset.m_RootIndex = -1;
            foreach (var edge in graph.Root.actionPort.connections)
            {
                if (edge.input.node is ExecutableNodeVisual enode)
                {
                    var index = graphnodes.IndexOf(enode.composite);
                    if (index != -1)
                    {
                        asset.m_RootIndex = index;
                        break;
                    }
                }
            }
            asset.m_RootPosition = graph.Root.GetPosition().position;
            asset.m_Nodes = graphnodes.ToArray();
            asset.m_Groups = groups.ToArray();
            var path = AssetDatabase.GetAssetPath(asset);
            if (string.IsNullOrEmpty(path) || !path.EndsWithIgnoreCase(".behaviourtree"))
            {
                asset.hideFlags |= UnityEngine.HideFlags.NotEditable;
                EditorUtility.SetDirty(asset);
                AssetDatabase.SaveAssetIfDirty(asset);
            }
            else
            {
                var data = JsonSerializedObject.ExportFrom(asset);
                NativeFileSystem.WriteAllText(path, data.ToJson(), Encoding.UTF8);
                AssetDatabase.ImportAsset(path);
            }
        }
    }
}
